package com.lemon.exam.service.impl;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.lemon.exam.common.constant.ApiConst;
import com.lemon.exam.common.entity.vo.StatisticsVO;
import com.lemon.exam.common.util.SecurityUtil;
import com.lemon.exam.service.HomeService;
import com.lemon.exam.service.IClassService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.r2dbc.core.DatabaseClient;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

import java.text.DecimalFormat;
import java.time.Duration;
import java.time.LocalDate;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

/**
 * 首页服务实现类
 *
 * @author Lemon
 * @since 2025/4/18 12:17
 */
@Slf4j
@Service
public class HomeServiceImpl implements HomeService {
    @Resource
    private DatabaseClient databaseClient;
    @Resource
    private IClassService classService;

    @Override
    public Mono<Map<String, Object>> getCurrentUser() {
        return SecurityUtil.getUserId().flatMap(userId -> Mono.zip(
                        this.getUserById(userId),
                        this.findRoleNameByUserId(userId),
                        classService.findClassNameByUserId(userId).collectList()).subscribeOn(Schedulers.boundedElastic())
                .map(tuple -> {
                    Map<String, Object> user = tuple.getT1();
                    user.put("roleNameList", tuple.getT2());
                    user.put("classNameList", tuple.getT3());
                    return user;
                }));
    }

    /**
     * 根据用户id获取用户信息
     *
     * @param userId 用户id
     * @return 用户信息
     */
    private Mono<Map<String, Object>> getUserById(Long userId) {
        return databaseClient.sql("""
                        SELECT
                            username,
                            `name`,
                            phone,
                            sex,
                            address,
                            SUBSTRING_INDEX(prev_login_ip, ',', 1) prevLoginIp,
                            SUBSTRING_INDEX(prev_login_time, ',', 1) prevLoginTime,
                            province_id provinceId,
                            city_id cityId,
                            county_id countyId
                        FROM
                            sys_user
                        WHERE
                            id = :id
                        LIMIT 1
                        """)
                .bind("id", userId)
                .fetch()
                .one()
                .subscribeOn(Schedulers.boundedElastic());
    }

    /**
     * 根据用户id获取角色名称
     *
     * @param userId 用户id
     * @return 角色名称
     */
    private Mono<List<String>> findRoleNameByUserId(Long userId) {
        return databaseClient.sql("""
                        SELECT
                            name
                        FROM
                            sys_role
                        WHERE
                            status = 1
                            AND deleted = 0
                            AND id IN (SELECT role_id FROM sys_user_role WHERE user_id = :userId)
                        """)
                .bind("userId", userId)
                .map(row -> row.get("name", String.class))
                .all()
                .collectList()
                .subscribeOn(Schedulers.boundedElastic());
    }

    @Override
    public Mono<Map<String, Object>> statisticsCurrentUser(String type) {
        return SecurityUtil.getUserId().flatMap(this::getCachedStatistics).flatMap(getStatistics(type));
    }

    /**
     * 统计用户信息缓存
     */
    private final Cache<Long, List<StatisticsVO>> statisticsCache = Caffeine.newBuilder()
            .expireAfterWrite(3, TimeUnit.MINUTES)
            .maximumSize(10000)
            .build();

    /**
     * 从缓存中获取用户信息，没有的话查询数据库
     *
     * @param userId
     * @return
     */
    private Mono<List<StatisticsVO>> getCachedStatistics(Long userId) {
        return Mono.fromCallable(() -> statisticsCache.get(userId, this::findAnswerByUserId)).subscribeOn(Schedulers.boundedElastic());
    }

    /**
     * 查询用户答题
     *
     * @param userId
     * @return
     */
    private List<StatisticsVO> findAnswerByUserId(Long userId) {
        return databaseClient.sql("""
                        SELECT
                            DATE_FORMAT(pa.submit_time, '%Y-%m-%d') AS submitTime,
                            IFNULL(pa.total_score, 0) AS userScore,
                            pa.passed AS passed,
                            p.total_score totalScore,
                            ROUND((SELECT AVG(a.total_score) FROM biz_paper_answer a WHERE a.paper_id = pa.paper_id AND a.total_score IS NOT NULL), 2) AS avgScore
                        FROM
                            biz_paper_answer pa
                            LEFT JOIN biz_paper p ON pa.paper_id = p.id
                        WHERE
                            pa.user_id = :userId
                        ORDER BY
                            pa.submit_time DESC
                        """)
                .bind("userId", userId)
                .map(row -> {
                    Integer passed = row.get("passed", Integer.class);
                    return new StatisticsVO(
                            row.get("submitTime", String.class),
                            row.get("userScore", Double.class),
                            passed == null ? null : passed == 1,
                            row.get("totalScore", Double.class),
                            row.get("avgScore", Double.class)
                    );
                })
                .all()
                .collectList()
                .switchIfEmpty(Mono.just(Collections.emptyList()))
                .subscribeOn(Schedulers.boundedElastic())
                .blockOptional(Duration.ofSeconds(10)).orElseGet(Collections::emptyList);
    }

    /**
     * 根据类型获取统计信息
     *
     * @param type
     * @return
     */
    @NotNull
    private Function<List<StatisticsVO>, Mono<? extends Map<String, Object>>> getStatistics(String type) {
        return list -> {
            if (CollectionUtils.isEmpty(list)) return Mono.just(Collections.emptyMap());
            return Mono.fromCallable(() -> switch (type) {
                case "all" -> Map.of("title", "全部成绩对比分析", "subtext", getPassRate(list), "data", list);
                case "year" -> getStatisticsByYear(list, LocalDate.now().format(ApiConst.DATE_FORMATTER_13));
                case "month" -> getStatisticsByMonth(list, LocalDate.now());
                default -> Collections.emptyMap();
            });
        };
    }

    /**
     * 获取用户信息-年度
     *
     * @param list
     * @return
     */
    private Map<String, Object> getStatisticsByYear(List<StatisticsVO> list, String year) {
        List<StatisticsVO> yearList = list.stream().filter(item -> item.getTime().startsWith(year)).toList();
        return Map.of(
                "title", "%s年度成绩对比分析".formatted(year),
                "subtext", getPassRate(yearList),
                "data", yearList
        );
    }

    /**
     * 获取用户信息-月度
     *
     * @param list
     * @param date
     * @return
     */
    private Map<String, Object> getStatisticsByMonth(List<StatisticsVO> list, LocalDate date) {
        String yearMonth = date.format(ApiConst.DATE_FORMATTER_1);
        List<StatisticsVO> yearMonthList = list.stream().filter(item -> item.getTime().startsWith(yearMonth)).toList();
        return Map.of(
                "title", "%s份成绩对比分析".formatted(date.format(ApiConst.DATE_FORMATTER_2)),
                "subtext", getPassRate(yearMonthList),
                "data", yearMonthList);
    }

    /**
     * 获取通过率
     *
     * @param list
     * @return
     */
    private String getPassRate(List<StatisticsVO> list) {
        //1.获取所有已阅卷的数据
        List<StatisticsVO> validPassedAnswers = list.parallelStream().filter(item -> Objects.nonNull(item.getPassed())).toList();
        if (CollectionUtils.isEmpty(validPassedAnswers)) {
            return "通过率: 0%";
        }

        //2.通过的个数
        long passedCount = validPassedAnswers.parallelStream().filter(StatisticsVO::getPassed).count();

        //3.计算通过率
        double value = passedCount * 1.0 / validPassedAnswers.size() * 100;

        //4.保留两位小数
        return "通过率: %s%%".formatted(new DecimalFormat("0.##").format(value));
    }
}
